This Technical Note describes the internal format of a MacPaint® document, which is a standard used by many other programs. This description is the same as that found in the “Macintosh Miscellaneous” section of early Inside Macintosh versions.
Changes since October 1988: Fixed bugs in the example code.
MacPaint documents are easy to read and write, and they have become a standard interchange format for fullΓÇôpage images on the Macintosh. This Note describes the MacPaint internal document format to help developers generate and interpret files in this format.
MacPaint documents have a file type of ΓÇ£PNTG,ΓÇ¥ and since they use only the data fork, you can ignore the resource fork. The data fork contains a 512ΓÇôbyte header followed by compressed data which represents a single bitmap (576 pixels wide by 720 pixels tall). At a resolution of 72 pixels per inch, this bitmap occupies the full 8 inch by 10 inch printable area of a standard ImageWriter printer page.
Header
The first 512 bytes of the document form a header of the following format:
ΓÇó 4ΓÇôbyte version number (default = 2)
ΓÇó 38*8 = 304 bytes of patterns
ΓÇó 204 unused bytes (reserved for future expansion)
As a Pascal record, the document format could look like the following:
MPHeader = RECORD
Version: LONGINT;
PatArray: ARRAY [1..38] of Pattern;
Future: PACKED ARRAY [1..204] of SignedByte;
END;
If the version number is zero, the document uses default patterns, so you can ignore the rest of the header block, and if your program generates MacPaint documents, you can write 512 bytes of zero for the document header. Most programs which read MacPaint documents can skip the header when reading.
Bitmap
Following the header are 720 compressed scan lines of data which form the 576 pixel wide by 720 pixel tall bitmap. Without compression, this bitmap would occupy 51,840 bytes and chew up disk space pretty fast; typical MacPaint documents compress to about 10K using the _PackBits procedure to compress runs of equal bytes within each scan line. The bitmap part of a MacPaint document is simply the output of _PackBits called 720 times, with 72 bytes of input each time.
To determine the maximum size of a MacPaint file, it is worth noting what Inside Macintosh says about _PackBits:
ΓÇ£The worst case would be when _PackBits adds one byte to the row
of bytes when packing.ΓÇ¥
If we include an extra 512 bytes for the file header information to the size of an uncompressed bitmap (51,840), then the total number of bytes would be 52,352. If we take into account the extra 720 ΓÇ£potentialΓÇ¥ bytes (one for each row) to the previous total, the maximum size of a MacPaint file becomes 53,072 bytes.
Reading Sample
PROCEDURE ReadMPFile;
{ This is a small example procedure written in Pascal that demonstrates
how to read MacPaint files. As a final step, it takes the data that
was read and displays it on the screen to show that it worked.
Caveat: This is not intended to be an example of good programming
practice, in that the possible errors merely cause the program to exit.
This is VERY uninformative, and there should be some sort of error handler
to explain what happened. For simplicity, and thus clarity, those types
of things were deliberately not included. This example will not work
on a 128K Macintosh, since memory allocation is done too simplistically.
}
CONST
DefaultVolume = 0;
HeaderSize = 512; { size of MacPaint header in bytes }
MaxUnPackedSize = 51840; { maximum MacPaint size in bytes }
{ 720 lines * 72 bytes/line }
VAR
srcPtr: Ptr;
dstPtr: Ptr;
saveDstPtr: Ptr;
lastDestPtr: Ptr;
srcFile: INTEGER;
srcSize: LONGINT;
errCode: INTEGER;
scanLine: INTEGER;
aPort: GrafPort;
theBitMap: BitMap;
BEGIN
errCode := FSOpen('MP TestFile', DefaultVolume, srcFile); { Open the file. }
IF errCode <> noErr THEN ExitToShell;
errcode := SetFPos(srcFile, fsFromStart, HeaderSize); { Skip the header. }
IF errCode <> noErr THEN ExitToShell;
errCode := GetEOF(srcFile, srcSize); { Find out how big the file is, }
IF errCode <> noErr THEN ExitToShell; { and figure out source size. }
srcSize := srcSize - HeaderSize ; { Remove the header from count. }
srcPtr := NewPtr(srcSize); { Make buffer just the right size. }
IF srcPtr = NIL THEN ExitToShell;
errCode := FSRead(srcFile, srcSize, srcPtr);{Read the data into the buffer.}
IF errCode <> noErr THEN ExitToShell; { File marker is past header. }
errCode := FSClose(srcFile); { Close the file we just read. }
IF errCode <> noErr THEN ExitToShell;
{ Create a buffer that will be used for the Destination BitMap. }
dstPtr := NewPtrClear(MaxUnPackedSize); {MPW library routine, see TN 219}
IF dstPtr = NIL THEN ExitToShell;
saveDstPtr := dstPtr;
{ Unpack each scan line into the buffer. Note that 720 scan lines are
guaranteed to be in the file. (They may be blank lines.) In the
UnPackBits call, the 72 is the count of bytes done when the file was
created. MacPaint does one scan line at a time when creating the file.
The destination pointer is tested each time through the scan loop.
UnPackBits should increment this pointer by 72, but in the case where
the packed file is corrupted UnPackBits may end up sending bits into
uncharted territory. A temporary pointer "lastDstPtr" is used for
testing the result.}
FOR scanLine := 1 TO 720 DO BEGIN
lastDstPtr := dstPtr;
UnPackBits(srcPtr, dstPtr, 72); { bumps both pointers }
IF ORD4(lastDstPtr) + 72 <> ORD4(dstPtr) THEN ExitToShell;
END;
{ The buffer has been fully unpacked. Create a port that we can draw into.
You should save and restore the current port. }
OpenPort(@aPort);
{ Create a BitMap out of our saveDstPtr that can be copied to the screen. }
theBitMap.baseAddr := saveDstPtr;
theBitMap.rowBytes := 72; { width of MacPaint picture }